home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
Libraries
/
VideoToolbox 96.06.15
/
VideoToolboxSources
/
NoisePdfFill.c
< prev
next >
Wrap
Text File
|
1996-04-03
|
17KB
|
420 lines
/*
NoisePdfFill.c
error=NoisePdfFill(world,&rect,pdfKind,&mean,&sd,min,max);
error=NoisePdfAdd(world,&rect,pdfKind,&mean,&sd,min,max);
Generate good noise images quickly. You supply a GWorld (or a window) and a rect
indicating how much of it to fill with noise. "world" may be a pointer to a
GWorld, a color CWindow, or, with casting, a plain-old Window. NoisePdfFill fills
the pixels within rect with noise; NoisePdfAdd adds noise to them. Each pixel
will get an independent integer sample from a specified probability density
function (pdf): kBinaryPdf, kUniformPdf, or kGaussianPdf. (A fourth pdf,
kBinomialPdf, is not recommended.) You specify the pdf kind, mean, and
"unclipped" standard deviation. The last two arguments, "min" and "max", are used
to clip, i.e. restrict the range of the pdf (e.g. to preclude overflow in your
image pixels). In the cases of kUniformPdf, kBinomialPdf, and kGaussianPdf,
samples falling outside the range [min,max] are discarded and replaced by
independent new samples. In the case of kBinary, each sample has one of only two
values, with equal probability; these two values are initially set to mean±sd, to
yield the desired mean and sd, but they are adjusted, if necessary, to bring them
within the range [min,max]. Upon return, the mean and sd arguments (which are
passed by reference, i.e. by address, not by value) return the mean and standard
deviation of the random generator of the samples (i.e. the clipped pdf).
The requested standard deviation, sd, must be greater than or equal to zero. The
special case of zero standard deviation is recognized and performed quickly,
producing a uniform image with specified mean. A request for an unachievably low,
but nonzero, sd will be served by providing the lowest achievable sd (given the
specified min and max), on the premis that the user cares most about log sd, so
that the least possible nonzero sd will be a better approximation to an
unachievably low sd than would zero sd. Supplying an sd of INF will produce
samples with values of min and max if the pdf kind is kBinaryPdf, and will
produce a uniform distribution over the range [min,max] if the pdf is
kUniformPdf, kGaussianPdf, or kBinomialPdf.
"max" must be greater than or equal to "mean", which must be greater than or
equal to "min". Normally, image pixels are considered positive and you'll want to
make "min" greater than or equal to zero, but this is not enforced by
NoisePdfFill; e.g. you can add zero-mean noise to a positive image.
Calling NoisePdfAdd with a "min"<0 should work perfectly (though it hasn't been
tested), but may be significantly slower than when "min"≥0. NoisePdfAdd() uses
NoisePdfFill() to make the noise in a temporary new GWorld and then calls
CopyWindows to add the noise to your image. If "min"≥0 then NoisePdfAdd() uses a
fast parallel addition that assumes there will be no overflow in each pixel's
arithmetic, so the user should take care to specify a "max" that will indeed
prevent overflow, since any overflow would affect a neighboring pixel. If "min"<0
then the unsigned addition of "negative" with positive pixels to produce positive
pixels will "overflow" in the normal course of computing the sum using unsigned
arithmetic. The bits of the pixel itself will be correct, but the overflow would
affect a neighboring pixel if parallel addition were used, so the additions are
done independently, one pixel at a time, which takes longer.
kBinaryPdf, kUniformPdf, and kGaussianPdf should all be useful. We use
kGaussianPdf in most of our noise-masking work, but for dynamic noise we
sometimes use kBinaryPdf in order to achieve more noise power, assuming that the
observer will visually integrate several of the dynamic frames, blurring the
distinction between pdf's. Presumably the three kinds of noise would be visually
indistinguishable if shown with independent 15 ms frames and equal sd.
kBinomialPdf works fine, but is not recommended. The kBinomialPdf code uses the
fact that the number of heads in 24 coin tosses is approximately gaussian. This
code was originally written as a quick way to make approximately gaussian noise.
However, kBinomialPdf is probably much slower than the subsequently written
kGaussianPdf, which provides samples that are accurately gaussian.
void GetGoodNoisePdfBounds(pdfKind,mean,clippedSd,&unclippedSd,&min,&max);
GetGoodNoisePdfBounds is a utility routine that helps you choose good parameters
with which to call NoisePdfFill or NoisePdfAdd. Be aware that, whereas
NoisePdfFill and NoisePdfAdd are general-purpose, the "good" in the name
"GetGoodNoisePdfBounds" indicates that its recommendations are not necessarily
best. It incorporates several reasonable but somewhat arbitrary assumptions, e.g.
that you'd like to clip your gaussian at ±2 sd, and helps you to choose a good
set of parameters to obtain that result. GetGoodNoisePdfBounds will not be
appropriate to all uses of NoisePdfFill and NoisePdfAdd; you may want to write
your own. Note: GetGoodNoisePdfBounds is still under development; I haven't yet
added its prototype to VideoToolbox.h. Beware that if the ±2*sd bounds are
clipped by the supplied min,max bounds then the returned unclippedSd will
be wrong. I wanted to add code to compute the exact answer, when it exists,
but the integrals for the gaussian case were sufficiently difficult that I
put it aside for some future date.
The hardest part of writing the code in this file was figuring out how to
separate the general-purpose code (i.e. NoisePdfFill) from the pragmatic but
somewhat arbitrary assumptions now encapsulated in GetGoodNoisePdfBounds.
HISTORY:
3/20/95 dgp wrote it, based on my MakeTextInNoiseWorld.c
4/8/95 dgp removed any assumption that world is a GWorldPtr; it can also
be a CWindowPtr or a WindowPtr.
4/18/95 dgp fixed error in NoisePdfAdd: mean & sd were swapped.
*/
#include "VideoToolbox.h"
OSErr NoisePdfFill(GWorldPtr world,Rect *rectPtr
,int pdfKind,double *meanPtr,double *sdPtr,int min,int max);
OSErr NoisePdfAdd(GWorldPtr world,Rect *rectPtr
,int pdfKind,double *meanPtr,double *sdPtr,int min,int max);
void GetGoodNoisePdfBounds(int pdfKind,double mean,double clippedSd
,double *unclippedSdPtr,int *minPtr,int *maxPtr);
//enum{kBinaryPdf=0,kBinomialPdf,kGaussianPdf,kUniformPdf}; // in VideoToolbox.h
#undef MAX
#undef MIN
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
static Boolean diagnostics=0;
OSErr NoisePdfFill(GWorldPtr world,Rect *rectPtr
,int pdfKind,double *meanPtr,double *sdPtr,int min,int max)
{
CGrafPtr oldPort;
GDHandle oldDevice;
int error=0;
OSErr osErr=0;
long n;
int i,j,x,y;
long low,high,sample;
double actualMean,actualSd; // mean and sd of process that generated the image
long actualMin,actualMax;
int binomialSamples;
double binomialFactor;
long extraSamples;
short *distribution;
static short **distributionHandle=NULL;
static long distributionSize;
static double lowSave,highSave,meanSave,unclippedSdSave,actualSdSave;
double meanSquare,a,aa;
RGBColor rgb;
GDHandle device;
int pixelSize;
static const RGBColor blackRGB={0,0,0},whiteRGB={0xFFFF,0xFFFF,0xFFFF};
unsigned long pix[2048];
register long *pL;
// Make noise. Takes 300 ms
assert(StackSpace()>4000);
assert(world!=NULL && sdPtr!=NULL && meanPtr!=NULL);
assert(!IsNan(*meanPtr) && !IsNan(*sdPtr));
assert(*sdPtr>=0.0);
assert(min<=*meanPtr && *meanPtr<=max);
high=max;
low=min;
if(IsInf(*sdPtr) && pdfKind!=kBinaryPdf)pdfKind=kUniformPdf;
switch(pdfKind){
case kBinaryPdf:
// high and low are values corresponding to heads and tails.
// The high-low difference is more important than their individual values.
if(!IsInf(*sdPtr)){
a=2*(*sdPtr);
low=MAX(round(*meanPtr-a/2),min);
high=MIN(round(low+a),max);
low=MAX(round(high-a),min);
if(low==high && *sdPtr>0){
if(high<max)high++;
else if(low>min)low--;
}
}
break;
case kUniformPdf:
if(!IsInf(*sdPtr)){
a=2*sqrt(3)*(*sdPtr)-1; // integer range of pdf for specified sd
low=MAX(round(*meanPtr-a/2),min);
high=MIN(round(low+a),max);
low=MAX(round(high-a),min);
if(low==high && *sdPtr>0){
if(high<max)high++;
else if(low>min)low--;
}
}
break;
default:
break;
}
GetGWorld(&oldPort,&oldDevice);
device=GetWindowDevice((WindowPtr)world);
pixelSize=(**(**device).gdPMap).pixelSize;
if(low==high){
SetGWorld(world,device);
Index2Color(low,&rgb);
RGBBackColor(&rgb);
EraseRect(rectPtr);
SetGWorld(oldPort,oldDevice);
actualMean=low;
actualSd=0;
goto done;
}
switch(pdfKind){
case kBinaryPdf:
SetGWorld(world,device);
Index2Color(low,&rgb);
RGBBackColor(&rgb);
Index2Color(high,&rgb);
RGBForeColor(&rgb);
error=NoiseFill(world,rectPtr,1,1,0);
RGBBackColor(&whiteRGB);
RGBForeColor(&blackRGB);
SetGWorld(oldPort,oldDevice);
actualSd=0.5*(high-low);
actualMean=0.5*(high+low);
break;
case kUniformPdf:
n=1+high-low;
assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
for(y=rectPtr->top;y<rectPtr->bottom;y++){
pL=(long *)pix;
for(x=rectPtr->right-rectPtr->left-1;x>=0;x--)
*pL++=low+nrand(n);
SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
,pix,rectPtr->right-rectPtr->left);
}
assert(n<=sizeof(pix)/sizeof(*pL));
pL=(long *)pix;
for(x=low;x<=high;x++)*pL++=x;
actualMean=MeanL((long *)pix,1+high-low,&actualSd);
if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f\n"
,*sdPtr,actualSd,actualSd/(*sdPtr));
break;
case kGaussianPdf:
/*
First we make an ordered list of about twenty thousand integers, with
frequency proportional to the desired probability. This table only needs
to be made once and can be reused indefinitely, until the parameters
(mean, sd, min, max) of the distribution change. Each noise pixel is
a random sample from the table.
*/
if(distributionHandle==NULL || low!=lowSave || high!=highSave
|| IsInf(*sdPtr)!=IsInf(unclippedSdSave)
|| (!IsInf(*sdPtr) && fabs(*sdPtr/unclippedSdSave-1)>.01)
|| *meanPtr!=meanSave){
distributionSize=256*100L;
if(distributionHandle==NULL){
distributionHandle=(short **)TempNewHandle(distributionSize*sizeof(*distribution),&osErr);
error=osErr;
if(distributionHandle==NULL){
distributionHandle=(short **)NewHandle(distributionSize*sizeof(*distribution));
error=MemError();
}else error=0;
if(distributionHandle==NULL)PrintfExit("Sorry, couldn't allocate %ld bytes "
"for a gaussian table. File “%s” line %d.\n"
,distributionSize*sizeof(*distribution),__FILE__,__LINE__);
}
if(fabs((high-*meanPtr)-(highSave-meanSave))>0.5 || high-low!=highSave-lowSave
|| IsInf(*sdPtr)!=IsInf(unclippedSdSave)
|| (!IsInf(*sdPtr) && fabs(*sdPtr/unclippedSdSave-1)>.01)){
// takes 300 ms
BoundedNormalIntegers(*distributionHandle,distributionSize
,*meanPtr,*sdPtr,low,high);
}else{
j=round(*meanPtr-meanSave);
distribution=*distributionHandle;
if(j!=0)for(i=0;i<distributionSize;i++)distribution[i]+=j;
*meanPtr=meanSave+j;
}
lowSave=low;
highSave=high;
unclippedSdSave=*sdPtr;
meanSave=*meanPtr;
actualMean=MeanW(*distributionHandle,distributionSize,&actualSdSave);
}
actualSd=actualSdSave;
assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
//HLock((Handle)distributionHandle);
distribution=*distributionHandle;
for(y=rectPtr->top;y<rectPtr->bottom;y++){
pL=(long *)pix;
for(x=rectPtr->right-rectPtr->left-1;x>=0;x--)
*pL++=distribution[nrand(distributionSize)];
SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
,pix,rectPtr->right-rectPtr->left);
}
if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f\n"
,*sdPtr,actualSd,actualSd/(*sdPtr));
break;
case kBinomialPdf:
// Use the number of heads in 24 coin flips as a random variable.
// It can be computed quickly and is approximately normal.
// Shift and scale to obtain the specified mean and variance.
// Discard samples outside the range [min,max].
// Return mean and sd of the clipped distribution.
binomialSamples=24; // should be even (for integer divide by 2)
binomialFactor=*sdPtr/sqrt(binomialSamples*0.25);
/*
The empirical approach of actually measuring the mean and variance
of our noise generator demands that we produce a
reasonable number of samples, at least 1000. Any extra samples are later
discarded.
*/
assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
extraSamples=ceil(1000.0/(rectPtr->bottom-rectPtr->top))
-(rectPtr->right-rectPtr->left);
if(extraSamples<0)extraSamples=0;
a=aa=0.0;
n=0;
for(y=rectPtr->top; y<rectPtr->bottom; y++){
pL=(long *)pix;
for(x=rectPtr->right-rectPtr->left-1; x>=-extraSamples; x--){
do{
sample=round(*meanPtr+binomialFactor
*(BinomialSampleQuickly(binomialSamples)-binomialSamples/2));
}while(sample<low || sample>high);
a+=sample;
aa+=(long)sample*(long)sample;
n++;
if(x>=0)*pL++=sample;
}
SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
,pix,rectPtr->right-rectPtr->left);
}
actualMean=a/n;
actualSd=sqrt(aa/n-actualMean*actualMean);
if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f, n %ld\n"
,*sdPtr,actualSd,actualSd/(*sdPtr),n);
break;
default:
PrintfExit("%s,%d: No such pdf %d\n",__FILE__,__LINE__,(int)pdfKind);
}
if(diagnostics){
ImageStatistics(world,rectPtr,&actualMin,&actualMax,&actualMean,&meanSquare);
if(actualMax>max || actualMin<min)printf("\nIMAGE OVERFLOW ERROR:\n");
printf("noise actualMin %ld≥%d, actualMax %ld≤%d, actualMean %.1f≈%.1f, rms %.1f≈%.1f≈%.1f\n"
,actualMin,(int)min
,actualMax,(int)max
,actualMean,*meanPtr
,sqrt(meanSquare),actualSd,*sdPtr);
assert(actualMax<=max && actualMin>=min);
}
done:
*sdPtr=actualSd;
*meanPtr=actualMean;
return error;
}
OSErr NoisePdfAdd(GWorldPtr world,Rect *rectPtr
,int pdfKind,double *meanPtr,double *sdPtr,int min,int max)
{
GWorldPtr noiseWorld;
GDHandle device;
int error=0,pixelSize;
double meanSquare,actualMean;
long actualMin,actualMax;
ColorTable **ct;
long copyMode;
assert(world!=NULL);
device=GetWindowDevice((WindowPtr)world);
pixelSize=(**(**device).gdPMap).pixelSize;
ct=GetCTable(pixelSize+32); // gray ramp
assert(ct!=NULL);
error=NewGWorld(&noiseWorld,pixelSize,rectPtr,ct,0,0);
if(error)error=NewGWorld(&noiseWorld,pixelSize,rectPtr,ct,0,useTempMem);
if(error)return error;
error=NoisePdfFill(noiseWorld,rectPtr,pdfKind,meanPtr,sdPtr,min,max);
if(error)goto done;
if(min>=0)copyMode=addOverParallelLiterally; // no overflow
else copyMode=addOver; // "sign" bit may overflow
error=CopyWindows(noiseWorld,world,rectPtr,rectPtr,copyMode,NULL);
if(error)PrintfExit("%s line %d: CopyWindows error %d\n",__FILE__,__LINE__,error);
if(diagnostics){
ImageStatistics(world,&world->portRect,&actualMin,&actualMax,&actualMean,&meanSquare);
if(actualMax>max || actualMin<min || diagnostics){
printf("signal+noise actualMin %ld, actualMax %ld, actualMean %.1f, actualSd %.1f\n"
,actualMin,actualMax,actualMean,sqrt(meanSquare-actualMean*actualMean));
}
}
done:
DisposeGWorld(noiseWorld);
return error;
}
void GetGoodNoisePdfBounds(int pdfKind,double mean,double clippedSd
,double *unclippedSdPtr,int *minPtr,int *maxPtr)
{
double dMax,dMin; // nominal range of difference from mean
double clippedSd1; // value of clippedSd that would be produced if unclippedSd were 1
int low,high; // range of difference from mean
assert(unclippedSdPtr!=NULL && minPtr!=NULL && maxPtr!=NULL);
assert(clippedSd>=0);
assert(*maxPtr>=*minPtr);
switch(pdfKind){
case kBinaryPdf:
dMax=clippedSd;
dMin=-dMax;
clippedSd1=1;
break;
case kUniformPdf:
dMax=sqrt(3)*clippedSd;
if(dMax>=1)dMax-=0.5;
dMin=-dMax;
clippedSd1=1;
break;
case kBinomialPdf:
dMax=2*clippedSd;
dMin=-dMax;
clippedSd1=0.85;
break;
case kGaussianPdf:
dMax=2*clippedSd;
dMin=-dMax;
clippedSd1=0.737;
break;
}
// high and low are the extremes of the pdf.
// The high-low difference is more important than the absolute value of either.
assert(dMax>=dMin);
low=MAX(round(mean+dMin),*minPtr);
high=MIN(round(low+(dMax-dMin)),*maxPtr);
low=MAX(round(high-(dMax-dMin)),*minPtr);
if(low==high && clippedSd>0){
if(high<*maxPtr)high++;
else if(low>*minPtr)low--;
}
*minPtr=low;
*maxPtr=high;
*unclippedSdPtr=clippedSd/clippedSd1;
}